package com.hoe.tsb.fraft.client.role;

import com.hoe.tsb.Configuration;
import com.hoe.tsb.fraft.Node;
import com.hoe.tsb.fraft.client.ClientRoleBridge;
import com.hoe.tsb.fraft.enums.RoleType;
import com.hoe.tsb.fraft.event.msg.Correct;
import com.hoe.tsb.fraft.event.msg.FRaftMessage;
import com.hoe.tsb.tool.SerialUtils;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 * 基础角色
 * </p>
 *
 * @author hoe
 * @version 1.0
 * @date 2022/11/28 14:36
 */
@Slf4j
public abstract class AbstractRole implements Role {

    /**
     * 配置类
     */
    protected final Configuration mConfig;
    /**
     * 角色切换观察者
     */
    protected final RoleObserver mRoleObserver;
    /**
     * 和客户端之间的连接桥梁，可以获取做一些回调操作
     */
    protected final ClientRoleBridge mBridge;
    /**
     * 超时线程的reset时间戳<br/>
     * 比如线程休眠30s，第15s时发现需要重置，则标记时间然后立马重置
     */
    private long mResetMill = 0L;

    public AbstractRole(ClientRoleBridge bridge, Configuration configuration, RoleObserver roleObserver) {
        this.mBridge = bridge;
        this.mConfig = configuration;
        this.mRoleObserver = roleObserver;
    }

    @Override
    public boolean handleMessage(FRaftMessage msg) {
        log.debug("收到消息开始处理：[{}]", msg);
        //TODO 判断当前状态是Follower，判断消息是否来自Leader，重置切换为Candidate的时间

        //TODO 创建并运行角色切换监听线程
        //创建超时检测线程


        /*
        需要定时的任务：
            发心跳、检测心跳

        需要分角色处理的消息
            拉票
            投票
            普通消息

        普通消息
            收到外部请求
            日志同步
                溯源数据
                区块生成

        角色切换逻辑
            Follower -> Candidate
                收到Leader的消息超时（定时器在一定范围内随机）
            Candidate -> Follower
                收到比当前term高的Leader任意消息
            Candidate -> Leader
                收到2f+1个Vote消息
            Leader -> Follower
                收到比当前term高的Leader任意消息

        如何标记消息来自哪个角色
            在消息体中设置消息来自的角色类型
         */

        if (msg.getTerm() < mRoleObserver.getTerm()) {
            Node leader = mBridge.getLeader();
            Correct correct = new Correct();
            correct.setLeaderIp(leader.getIp());
            correct.setLeaderPort(leader.getPort());
            mBridge.sendToSpecific(correct, msg.getNodes().get(0).getIp(), msg.getNodes().get(0).getPort());
            log.warn("收到低于当前任期[{}]的消息，任期[{}]，类型[{}]，已发送纠正消息。当前leader的ip[{}]，端口[{}]",
                    mRoleObserver.getTerm(), msg.getTerm(), msg.getType(), leader.getIp(), leader.getPort());
            //收到低于任期的消息会被忽略，应继续处理
            //return true;
        }

        switch (msg.getType()) {
            /*
             * 纠错消息
             */
            case CORRECT:
                Correct correct = SerialUtils.deserializeIFRaftMsg(msg.getBody(), Correct.class);
                mBridge.markLeader(correct.getLeaderIp(), correct.getLeaderPort());
                mRoleObserver.setTerm(msg.getTerm());
                mRoleObserver.turnTo(RoleType.FOLLOWER);
                return true;
            /*
             * 普通消息-溯源
             */
            case TRACE:
                //TODO 判断当前是否是候选者，如果是则判断任期号，如果小于则直接拒绝，如果大于或等于则切换为跟随者

                //TODO 处理普通消息

                break;
            /*
             * 普通消息-区块
             */
            case BLOCK:
                break;
        }

        return false;
    }

//抽象出三个角色的处理器
    //'interface Role' with methods
    // 'void handleHeartbeat(Node)' ,
    // 'void handleTrace(Trace)' , put it to trace pool
    // 'void handleBlock(Block)' , one block multiple trace, insert block-hash:block and trace-hash:trace besides
    // 'void reset'

    //Follower
    //

    //Candidate

    //Leader

    //构建Exchanger

    //定义mListeners，并实现setListener方法，本身也实现FRaftClientListener，在监听方法内部依次回调

    //实现Entity到byte[]的转换

    //实现broadcast方法

    //

    @Override
    public void onTimeout() {
        if (mResetMill != 0L) {
            mResetMill = 0;
            return;
        }
        log.debug("onTimeout 超时触发，开始调用对应角色的超时逻辑");
        //调用具体角色进行处理
        doTimeout();
    }

    /**
     * 切换到角色
     * @param roleType 待切换到的角色类型枚举
     */
    protected void turnTo(RoleType roleType) {
        mRoleObserver.turnTo(roleType);
        //角色切换后，自动调用重置超时，方法内部无需调用
        resetTimeout();
    }

    /**
     * 重置超时逻辑
     */
    protected void resetTimeout() {
        log.debug("重置超时时间");
        mResetMill = System.currentTimeMillis();
        synchronized (RoleAgent.class) {
            try {
                //如果在等待中途被直接唤醒，会在role的onTimeout中被排除掉不会执行实际的超时逻辑
                RoleAgent.class.notifyAll();
            } catch (Exception e) {
                //ignore ...
            }
        }
    }

    /**
     * 实际执行超时回调逻辑
     */
    protected abstract void doTimeout();

//    /**
//     * 处理拉票消息
//     * @param canvass 拉票消息
//     */
//    protected abstract void handleCanvass(Canvass canvass);
//
//    /**
//     * 处理投票消息
//     * @param vote 投票消息
//     */
//    protected abstract void handleVote(Vote vote);
//
//    /**
//     * 处理溯源消息消息
//     * @param trace 溯源数据消息
//     */
//    protected abstract void handleTrace(Trace trace);
//
//    /**
//     * 处理区块消息消息
//     * @param block 区块数据消息
//     */
//    protected abstract void handleBlock(Block block);

}
